/**
 * 
 */
package edu.arizona.bio5.onto.decomposition;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.OWLAnnotationAssertionAxiom;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLDeclarationAxiom;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.modularity.OntologySegmenter;

import uk.ac.manchester.cs.graph.Edge;
import uk.ac.manchester.cs.graph.alg.TransitiveReductionAlgorithm;
import uk.ac.manchester.cs.graph.alg.TransitiveReductionAlgorithm.ReductionHandler;
import uk.ac.manchester.cs.owlapi.modularity.ModuleType;
import uk.ac.manchester.cs.owlapi.modularity.SyntacticLocalityModuleExtractor;
import uk.ac.manchester.cs.util.SetUtils;

/**
 * @author Pavel Klinov
 *
 * pklinov@cs.man.ac.uk, pavel@clarkparsia.com
 * 
 * Mar 5, 2011
 */
public class DmitryDecompositionAlgorithm implements ADAlgorithm {

	private ModuleType mModType = null;
	
	public DmitryDecompositionAlgorithm() {
		mModType = ModuleType.BOT;
	}
	
	public DmitryDecompositionAlgorithm(ModuleType modType) {		
		mModType = modType;
	}
	
	public AtomicDecomposition decompose(OWLOntologyManager manager, OWLOntology ontology, OntologySegmenter mainSegmenter, Set<OWLAxiom> toDoAxioms) {
		final AtomicDecomposition ad = new AtomicDecomposition(ontology.getOntologyID().getOntologyIRI().toURI(), mModType);
		Set<OWLAxiom> minusTautologies = mainSegmenter.extract(ontology.getSignature()); 
		Set<OWLAxiom> tautologies = SetUtils.difference(toDoAxioms, minusTautologies);		
		//Ignore syntactic tautologies - they are never a part of any module	
		toDoAxioms.retainAll(minusTautologies);		
		System.out.println(toDoAxioms.size() + " axioms after removing syntactic tautologies");		
		//Eliminate axioms which are not local w.r.t. the empty signature. 
		Set<OWLAxiom> globalAxioms = mainSegmenter.extract(new HashSet<OWLEntity>()); 
		
		toDoAxioms.removeAll(globalAxioms);
		System.out.println(toDoAxioms.size() + " axioms after removing global axioms");
		//Compute all atoms
		try {
			decompose(toDoAxioms, ad, globalAxioms);
		} catch (OWLOntologyCreationException e) {
			throw new RuntimeException(e);
		}
		
		System.out.println("Removing redundant edges...");
		//Remove redundant edges
		new TransitiveReductionAlgorithm<Atom, Object>(new ReductionHandler<Atom, Object>() {

			@Override
			public void onEdgeRemove(Edge<Atom, Object> edge) {
				ad.removeDependency(edge.getStart(), edge.getEnd());
			}})
			.compute(ad.getGraph());
		//Adding global axioms and syntactic tautologies
		ad.setGlobalAxioms(globalAxioms);
		ad.setTautologies(tautologies);
		//Finally, add entity annotations to atoms
		addAnnotations(ad, ontology);
		
		System.out.println("Decomposed!");
		
		return ad;
	}
	
	/*
	 * Decomposes a set of axioms and returns a set of top-level atoms
	 */
	private Set<MemAtom> decompose (Set<OWLAxiom> toDoAxioms, AtomicDecomposition ad, Set<OWLAxiom> globalAxioms) throws OWLOntologyCreationException {
		Set<MemAtom> topAtoms = new HashSet<MemAtom>();
		OWLOntologyManager mgr = OWLManager.createOWLOntologyManager();
		OntologySegmenter extractor = new SyntacticLocalityModuleExtractor(mgr, 
										mgr.createOntology(SetUtils.union(toDoAxioms, globalAxioms)),
										mModType);		
		
		while (!toDoAxioms.isEmpty()) {
			//Take an axiom
			OWLAxiom alpha = toDoAxioms.iterator().next();
			Set<OWLAxiom> alphaModule = SetUtils.difference(extractor.extract(alpha.getSignature()), globalAxioms);
			MemAtom alphaAtom = ad.getAtomForAxiom(alpha) == null ? initAtom(ad, alpha) : (MemAtom)ad.getAtomForAxiom(alpha);
			
			for (OWLAxiom beta : alphaModule) {
				Atom betaAtom = ad.getAtomForAxiom(beta);
				
				if (betaAtom == alphaAtom) continue;
				
				Set<OWLAxiom> betaModule = extractor.extract(beta.getSignature());
				
				if (betaModule.contains(alpha)) {
					//beta generates the same atom
					//so add it to the alpha atom or merge atoms if 
					//an atom for beta already exists
					if (betaAtom != null) {
						ad.mergeAtoms(alphaAtom, betaAtom);
					}
					else {
						ad.addAxiomToAtom(beta, alphaAtom);
					}
				}
				else {
					//alpha depends on all top atoms in the beta module
					if (betaAtom != null) {
						ad.addDependency(alphaAtom, betaAtom);
					}
					else {
						Set<MemAtom> depAtoms = decompose(SetUtils.difference(betaModule, globalAxioms), ad, globalAxioms); 
						
						for (Atom depAtom : depAtoms) {
							ad.addDependency(alphaAtom, depAtom);
						}
					}
				}
			}
			
			toDoAxioms.removeAll(alphaModule);
			topAtoms.add(alphaAtom);
		}
		
		return topAtoms;
	}
	
	
	private MemAtom initAtom(AtomicDecomposition ad, OWLAxiom alpha) {
		Set<OWLAxiom> atomSet = new HashSet<OWLAxiom>(Collections.singleton(alpha));
		
		return ad.addAtom( atomSet );
	}

	/* 
	 */
	@Override
	public AtomicDecomposition decompose(OWLOntologyManager manager, OWLOntology ontology) {
		Set<OWLAxiom> axioms = new HashSet<OWLAxiom>(ontology.getLogicalAxioms());
		
		return decompose(manager, ontology, new SyntacticLocalityModuleExtractor(manager, ontology, mModType), axioms);
	}

	private void addAnnotations(AtomicDecomposition ad, OWLOntology ontology) {
		for (Atom atom : ad.getAtoms()) {
			//Entity annotations
			for (OWLEntity term : atom.getSignature()) {
				for (OWLAnnotationAssertionAxiom annAxiom : ontology.getAnnotationAssertionAxioms(term.getIRI())) {
					atom.addNonLogicalAxiom(annAxiom);
				}
				
				for (OWLDeclarationAxiom decl : ontology.getDeclarationAxioms(term)) {
					atom.addNonLogicalAxiom(decl);
				}
			}
		}
	}
}